#include "Ground.h"

#include "../../Game.h"
#include "../../PhysicsLogic.h"
#include "../../Textures.h"
#include "../../Renderer.h"
#include "../../Util/DiamondSquareAlgorithm.h"
#include "../../Util/Refine.h"

SurfaceRenderingData Ground::srd;
LinkedList<Ground*> Ground::all;
LinkedList<Ground*> Ground::toDelete;

void Ground::Load() { //static
	srd.SetTexture(Textures::TEXTURE_GROUND);
	const float brightness = 0.8f;
	srd.SetColor(brightness, brightness, brightness, 1);
}

Ground::Ground(NxU32 cellCountX, NxU32 cellCountZ, NxU32 planeDistance, float roughness, float cellsPerTexture, const NxVec3& scale) : 
		cellCountX(cellCountX),
		cellCountZ(cellCountZ),
		cellsPerTexture(cellsPerTexture),
		rx(cellCountX / cellsPerTexture),
		rz(cellCountZ / cellsPerTexture),
		tx((cellCountX + 1) / rx),
		tz((cellCountZ + 1) / rz) {

	Game::log.LogDebug("Terrain: generating...");

	const NxU32 sampleCountX = cellCountX+1;
	const NxU32 sampleCountZ = cellCountZ+1;

	NxHeightFieldDesc heightFieldDesc;
	heightFieldDesc.nbRows = sampleCountX;
	heightFieldDesc.nbColumns = sampleCountZ;
	heightFieldDesc.thickness = -1000.0f;
	heightFieldDesc.convexEdgeThreshold = 0;
	heightFieldDesc.samples = new NxU32[sampleCountX * sampleCountZ];
	heightFieldDesc.sampleStride = sizeof(NxU32);

	DynamicArray2<float> heights(sampleCountZ, sampleCountX);

	//make a random terrain:
	const float cornerHeight = 0.5f;
	DiamondSquareAlgorithm::Generate(heights, roughness, cornerHeight);

	//make a plane in the middle:
	for (NxU32 x = 0; x < sampleCountX; x++) {
		for (NxU32 z = 0; z < sampleCountZ; z++) {
			const int cz = z - sampleCountZ/2;
			const int cx = x - sampleCountX/2;
			float dist = NxMath::sqrt(float(cz*cz*2+cx*cx)) - planeDistance;

			if(dist <= 0) {
				heights(z, x) = 0;
			} else {
				if(dist > planeDistance*2.0f) {
					dist = planeDistance*2.0f;
				}
				heights(z, x) *= dist/10000.0f;
			}
		}
	}

	//set samples:
	const HeightFieldElevationType maxHeight = std::numeric_limits<HeightFieldElevationType>::max();
	NxU8* currentByte = (NxU8*)heightFieldDesc.samples;
	for (NxU32 x = 0; x < sampleCountX; x++) {
		for (NxU32 z = 0; z < sampleCountZ; z++) {
			NxHeightFieldSample* currentSample = (NxHeightFieldSample*)currentByte;
			currentSample->height = (HeightFieldElevationType)(heights(z, x)*maxHeight);
			currentSample->materialIndex0 = 0;
			currentSample->tessFlag = 0;
			currentByte += heightFieldDesc.sampleStride;
		}
	}

	NxHeightField* heightField = PhysicsLogic::GetPhysicsSDK()->createHeightField(heightFieldDesc);

	delete[] heightFieldDesc.samples;

	NxHeightFieldShapeDesc heightFieldShapeDesc;
	heightFieldShapeDesc.heightField = heightField;
	heightFieldShapeDesc.heightScale = scale.y;
	heightFieldShapeDesc.rowScale = scale.x;
	heightFieldShapeDesc.columnScale = scale.z;
	heightFieldShapeDesc.materialIndexHighBits = 0;
	heightFieldShapeDesc.holeMaterial = 2;

	NxActorDesc actorDesc;
	actorDesc.shapes.pushBack(&heightFieldShapeDesc);
	actorDesc.globalPose.t = NxVec3(cellCountX/2*-scale.x, -0.01f, cellCountZ/2*-scale.z);
	actorDesc.userData = this;

	actor = PhysicsLogic::GetScene()->createActor(actorDesc);
	actor->setGroup(1);
	actor->setContactReportFlags(NX_NOTIFY_FORCES);
	actor->getShapes()[0]->setFlag(NX_SF_VISUALIZATION, false);

	//prepare rendering buffers:

	NxHeightFieldShape* hfs = actor->getShapes()[0]->isHeightField();

	NxHeightField& hf = hfs->getHeightField();
	NxU8* samples = (NxU8*)hf.getCells();
	const NxU32 sampleStride = hf.getSampleStride();

	NxReal sx = hfs->getRowScale();
	NxReal sy = hfs->getHeightScale();
	NxReal sz = hfs->getColumnScale();
	
	NxU32 nbIndices = cellCountX*cellCountZ * 2 * 3;
	NxU32 nbVertices = cellCountX*cellCountZ * 4;

	NxU32 *indices = new NxU32[nbIndices];
	NxVec3 *vertices = new NxVec3[nbVertices];
	NxVec3 *normals = new NxVec3[nbVertices];
	NxVec3 *colors = new NxVec3[nbVertices];
	NxReal *texCoords = new NxReal[nbVertices*2];

	NxU32 verticesCount = 0;
	NxU32 texCoordsCount = 0;
	NxU32 indicesCount = 0;

	const float cStart = 0.6f;
	const float cFactor = 500.0f;
	const float cMin = 0.6f;
	const float cMax = 1.0f;

	for (NxU32 x=0; x<cellCountX; ++x) {
		for (NxU32 z=0; z<cellCountZ; ++z) {
			const NxU32 i0 = z + x * (cellCountZ+1);
			const NxU32 i1 = i0 + (cellCountZ+1);
			const NxU32 i2 = i0 + 1;
			const NxU32 i3 = i1 + 1;

			const NxHeightFieldSample* s0 = GetSampleFromIndex(samples, i0, sampleStride);
			const NxHeightFieldSample* s1 = GetSampleFromIndex(samples, i1, sampleStride);
			const NxHeightFieldSample* s2 = GetSampleFromIndex(samples, i2, sampleStride);
			const NxHeightFieldSample* s3 = GetSampleFromIndex(samples, i3, sampleStride);

			const NxVec3 v0(x*sx, s0->height*sy, z*sz);
			const NxVec3 v1((x+1)*sx, s1->height*sy, z*sz);
			const NxVec3 v2(x*sx, s2->height*sy, (z+1)*sz);
			const NxVec3 v3((x+1)*sx, s3->height*sy, (z+1)*sz);

			const NxVec3 n0(hfs->getNormalAtShapePoint(v0.x, v0.z));
			const NxVec3 n1(hfs->getNormalAtShapePoint(v1.x, v1.z));
			const NxVec3 n2(hfs->getNormalAtShapePoint(v2.x, v2.z));
			const NxVec3 n3(hfs->getNormalAtShapePoint(v3.x, v3.z));

			const float c0 = refine(float(cStart+heights[i0]*cFactor), cMin, cMax);
			const float c1 = refine(float(cStart+heights[i1]*cFactor), cMin, cMax);
			const float c2 = refine(float(cStart+heights[i2]*cFactor), cMin, cMax);
			const float c3 = refine(float(cStart+heights[i3]*cFactor), cMin, cMax);

			const bool borderX = x%tx == tx-1;
			const bool borderZ = z%tz == tz-1;

			//vertices
			vertices[verticesCount] = v0;
			vertices[verticesCount+1] = v1;
			vertices[verticesCount+2] = v2;
			vertices[verticesCount+3] = v3;

			//normals
			normals[verticesCount] = n0;
			normals[verticesCount+1] = n1;
			normals[verticesCount+2] = n2;
			normals[verticesCount+3] = n3;

			//colors
			colors[verticesCount] = NxVec3(c0, 1, c0);
			colors[verticesCount+1] = NxVec3(c1, 1, c1);
			colors[verticesCount+2] = NxVec3(c2, 1, c2);
			colors[verticesCount+3] = NxVec3(c3, 1, c3);

			//texCoords
			texCoords[texCoordsCount++] = GetTextureCoordinateX(x, borderX);
			texCoords[texCoordsCount++] = GetTextureCoordinateZ(z, borderZ);

			texCoords[texCoordsCount++] = GetTextureCoordinateX(x+1, borderX);
			texCoords[texCoordsCount++] = GetTextureCoordinateZ(z, borderZ);

			texCoords[texCoordsCount++] = GetTextureCoordinateX(x, borderX);
			texCoords[texCoordsCount++] = GetTextureCoordinateZ(z+1, borderZ);

			texCoords[texCoordsCount++] = GetTextureCoordinateX(x+1, borderX);
			texCoords[texCoordsCount++] = GetTextureCoordinateZ(z+1, borderZ);
		
			//left triangle
			indices[indicesCount++] = verticesCount;
			indices[indicesCount++] = verticesCount+2;
			indices[indicesCount++] = verticesCount+1;

			//right triangle
			indices[indicesCount++] = verticesCount+1;
			indices[indicesCount++] = verticesCount+2;
			indices[indicesCount++] = verticesCount+3;


			verticesCount = verticesCount + 4;
		}
	}

	glGenBuffers(1, &vboIndexBuffer);
	glGenBuffers(1, &vboVertexBuffer);
	glGenBuffers(1, &vboNormalBuffer);
	glGenBuffers(1, &vboColorBuffer);
	glGenBuffers(1, &vboTexCoordBuffer);

	glBindBuffer(GL_ARRAY_BUFFER, vboVertexBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(NxVec3)*nbVertices, &vertices[0].x, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vboNormalBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(NxVec3)*nbVertices, &normals[0].x, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vboColorBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(NxVec3)*nbVertices, &colors[0].x, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vboTexCoordBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(NxReal)*nbVertices*2, &texCoords[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndexBuffer);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(NxU32)*nbIndices, &indices[0], GL_STATIC_DRAW);

	delete[] indices;
	delete[] vertices;
	delete[] normals;
	delete[] colors;
	delete[] texCoords;

	all.Add(this);

	Game::log.LogDebug("Terrain: done.");
}

Ground::~Ground() {
	glDeleteBuffers(1, &vboVertexBuffer);
	glDeleteBuffers(1, &vboNormalBuffer);
	glDeleteBuffers(1, &vboColorBuffer);
	glDeleteBuffers(1, &vboTexCoordBuffer);
	glDeleteBuffers(1, &vboIndexBuffer);

	NxHeightField& heightField = actor->getShapes()[0]->isHeightField()->getHeightField();
	PhysicsLogic::GetScene()->releaseActor(*actor);
	PhysicsLogic::GetPhysicsSDK()->releaseHeightField(heightField);
}

void Ground::Draw() {
	NxHeightFieldShape* hfs = actor->getShapes()[0]->isHeightField();

	glPushMatrix();

	Renderer::SetMaterial(srd);

	float glMat[16];
	hfs->getGlobalPose().getColumnMajor44(glMat);
	glMultMatrixf(glMat);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glBindBuffer(GL_ARRAY_BUFFER, vboVertexBuffer);
	glVertexPointer(3, GL_FLOAT, 0, 0);

	glBindBuffer(GL_ARRAY_BUFFER, vboNormalBuffer);
	glNormalPointer(GL_FLOAT, 0, 0);

	glBindBuffer(GL_ARRAY_BUFFER, vboColorBuffer);
	glColorPointer(3, GL_FLOAT, 0, 0);

	glBindBuffer(GL_ARRAY_BUFFER, vboTexCoordBuffer);
	glTexCoordPointer(2, GL_FLOAT, 0, 0);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndexBuffer); 
	glDrawElements(GL_TRIANGLES, cellCountX*cellCountX * 2 * 3, GL_UNSIGNED_INT, 0);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 

	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);

	glPopMatrix();
}

